package com.ch3cooh.serviceuser.tool;

import java.util.Date;
import java.time.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 雪花算法, 一共64位，
 * 从高位到低位:
 * 1位: 保留位，为0表示正数，不使用
 *41位：时间戳(毫秒级)，当前时间戳-起始时间戳(这个算法在第一次使用时的时间戳)
 *10位：机器id,可支持 2^10个机器 ， 5位(32)的dataCenterId和5位(32)的workId
 * 12位：毫秒级序列号，1个机器在1个毫秒内最多可产生2^12=4096个序列号
 */
public class IdGenerator{

    private final ReentrantLock lock = new ReentrantLock();

    /** 开始时间戳 2018-01-01 00:00:00.0*/
    private long startTimeStamp = 1514736000000L;

    /** workId所需位数，5位*/
    private final long workIdBits = 5L;

    /** 数据节点所需位数，5位 */
    private final long dataCenterIdBits = 5L;

    /** 序列号位数*/
    private final long sequenceBits = 12L;

    /** 序列号掩码 */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 支持的最大workId，即 5^2-1 = 31 */
    private final long maxWorkId = -1L ^ (-1L << workIdBits);

    /** 支持的最大数据节点，即 5^2-1 = 31 */
    private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    /** 因为最后 时间戳+机器id+序列号 = 分布式id ,要拼成这样的id，需要机器id左移序列号的长度，时间戳左移机器id加序列号的长度 */
    /** workId左移位数 */
    private final long workIdShift = sequenceBits;

    /** 数据节点左移位数*/
    private final long dataCenterShift = sequenceBits + dataCenterIdBits;

    /** 时间戳左移位数*/
    private final long timeStampShift = sequenceBits + dataCenterIdBits + workIdBits;

    /** 工作机器实例id 0-31 */
    private long workId;

    /** 数据节点id 0-31 */
    private long dataCenterId;

    /** 序列号 0-4095*/
    private long sequenceId;

    /** 上次生成ID的时间截 */
    private long lastTimeStamp;



    public IdGenerator(long workId, long dataCenterId) {
        if(workId < 0 || workId > maxWorkId){
            throw new IllegalArgumentException( String.format("workId取值范围在0~%d",maxWorkId));
        }
        if(dataCenterId < 0 || dataCenterId >maxDataCenterId){
            throw new IllegalArgumentException( String.format("dataCenterId取值范围在0~%d",maxDataCenterId));
        }
        this.workId = workId;
        this.dataCenterId = dataCenterId;
    }

    /**
     * 生成id
     * @return  64位id
     */
    public long genId() {
        long currentTimeStamp = genTime();
        //先判断 当前时间戳是否早于 上一次请求的时间戳
        if (currentTimeStamp < lastTimeStamp) {
            throw new RuntimeException(String.format("Clock moved backwards . Refusing to generate id , currentTimeStamp = %d , lastTimeStamp = %d", currentTimeStamp, lastTimeStamp));
        }
        //判断 当前时间戳与上次时间戳 是否在同一毫秒内, 序列号从1开始用，序列号为0表示溢出
        if (currentTimeStamp == lastTimeStamp) {
            lock.lock();
            try {
                sequenceId = (sequenceId + 1) & sequenceMask;
            }catch (Exception e){
                System.err.println("获取序列号发生错误");
                return -1;
            }finally{
                lock.unlock();
            }

            if (sequenceId == 0) {
                currentTimeStamp = nextMs(lastTimeStamp);
            }
        } else {  //当前时间戳与上次时间戳 不在同一毫秒，序列号清零
            sequenceId = 0L;
        }

        //各 字段都已经生成完毕，组合即可
        lastTimeStamp = currentTimeStamp;       //将当前时间戳记录下来，供下次生成id时候作为上次时间戳使用
        //1514736000000L        1524474333151
        return ((currentTimeStamp - startTimeStamp) << timeStampShift)
                | (dataCenterId << dataCenterShift)
                | (workId << workIdShift)
                | sequenceId;
    }

    /**
     * 反解主键，获得生成时间
     * @param id        雪花算法生成的主键
     * @return  LocalDateTime
     */
    public LocalDateTime parseId(long id){
        //check
        long lastTime = getvalueBetween(id, timeStampShift+(63-timeStampShift-1), timeStampShift);

        long timeStamp = startTimeStamp + lastTime;
        Instant instant =  new Date(timeStamp).toInstant();
        ZoneId zoneId = ZoneId.systemDefault();
        return instant.atZone(zoneId).toLocalDateTime();
    }

    /**
     * 获取一个时间戳
     * @return  当前时间的时间戳
     */
    private long genTime() {
        return System.currentTimeMillis();
    }

    /**
     * 循环获取当前时间戳，直到不等于 上次时间戳时 返回
     * @param lastTimeStamp     上次时间戳
     * @return  当前时间戳
     */
    private long nextMs(long lastTimeStamp) {
        long currentTimeStamp = genId();
        while( currentTimeStamp == lastTimeStamp ){
            currentTimeStamp = genId();
        }
        return currentTimeStamp;
    }



/*
    public static void main(String[] args) {
        LocalDateTime time = LocalDateTime.of(LocalDate.of(2018, 1, 1), LocalTime.of(0, 0));
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = time.atZone(zoneId);
        Date from = Date.from(zdt.toInstant());
        long timeStamp = from.getTime();
        System.out.println(timeStamp);
    }
*/

    public static  long getvalueBetween(long value,long left ,long right){
        long mask =  -1L ^(-1L << left);
   //     System.out.println(mask);
        mask >>= right;
        mask <<= right;
        long l = (value & mask) >> right;
        return l;
    }

    public static void main(String[] args) {
        long a =0;
        short b = -1 ^ (-1 << 8);
//        b >>= 4;
//        b <<=4;
//        a &=b;
//        a >>=4;
    //    System.out.println( Long.toBinaryString(a));
        System.out.println( Long.toBinaryString( getvalueBetween(a,8,4) ));
    }

}
